/**
 ******************************************************************************
 * @file lcd.c
 * @author GX (2524913364@qq.com)
 * @brief 	粤嵌GEC6818开发板LCD库源码。使用显存映射，可选1～3个显存大小
 * 					默认功能：绘制基本几何图形，
 * 					可选功能：显示bmp图片、显示ASCII字符串。
 * 					
 * 					屏幕扫描方向：
 * 					○ x →
 *					y
 * 					↓
 * 
 * @version 2.1.2
 * @date 2023-09-03
 * 
 * @copyright Copyright (c) 2022
 * @note	此模块基于系统IO.
 * 				显示屏文件可以重复打开得到不同的文件描述符，而且都可以显存映射到不同的内存区，
 * 				并且对不同的映射内存写入内容都可以改变显示屏的内容。
 * 
 ******************************************************************************
 *----------------------------------------------------------------------------*
 *  Remark         : Description                                              *
 *----------------------------------------------------------------------------*
 *  Change History :                                                          *
 *  <Date>     | <Version> | <Author>       | <Description>                   *
 *----------------------------------------------------------------------------*
 *  2022-07-01 | 1.0.0     | GX             | Create file                     *
 *----------------------------------------------------------------------------*
 *  2022-07-02 | 1.0.1     | GX							| Rich functions                  * 
 *----------------------------------------------------------------------------*
 *  2022-07-09 | 2.0.0     | GX							| Support display characters,and	*
 * 						 |           |                | must add mode <fonts.h>					*
 *----------------------------------------------------------------------------* 
 *  2022-07-26 | 2.1.0     | GX             | add function:                   *  
 *             |           |                | lcd_show_bmp_rmColor()          *
 *             |           |                | lcd_show_buf()  								*
 * 						 |           |                | lcd_bmp_to_Buf()                *
 *----------------------------------------------------------------------------* 
 *  2022-08-16 | 2.1.1     | GX             | delete some show flag functions * 
 *             |           |                | add init function               *
 *----------------------------------------------------------------------------* 
 *  2023-09-03 | 2.1.2     | GX             | Refactoring code                * 
 *             |           |                | rational_rectangle              *
 *----------------------------------------------------------------------------* 
 ******************************************************************************
 */

/*----------------------------- start of file -------------------------------*/


#include "lcd.h"


#ifdef USE_FONTS
static sFONT *LCD_Currentfonts = &Font32x48;  //英文字体
static uint32_t CurrentTextColor   = LCD_BLACK;//前景色
static uint32_t CurrentBackColor   = LCD_CYAN;//背景色
static bool useBackColor = false;
#endif /* USE_FONTS */


/* 用来切换可见区用户使用的时候尽管 自加 实现切换即可，不用担心溢出之类的事情 */
uint32_t g_view_block = 0;
int32_t g_fd_lcd;												/* lcd 文件描述符 */
int32_t *g_lcd_fb;											/* 显存映射的基地址 */
struct fb_var_screeninfo g_vinfo;         /* 显卡设备的可变属性结构体 */

static void rational_rectangle(int32_t *x, int32_t *y, 
															 int32_t *wide, int32_t *height);

/**
 * @brief 初始化lcd,初始化后有三个关键的变量 g_fd_lcd、g_fb_base、g_vinfo
 * 				这三个变量在使用lcd的过程中会一直使用
 * @return 
 */
bool lcd_init()
{
	/* 打开文件 */
	g_fd_lcd = open(LCD_PATH, O_RDWR);
	if (g_fd_lcd == -1)
	{
		perror("open lcd");
		return false;
	}

	/* 显存映射 */
	g_lcd_fb = mmap(NULL, LCD_BUFF_SIZE*LCD_BLOCK,  PROT_READ|PROT_WRITE,
				MAP_SHARED, g_fd_lcd, 0);
	if (g_lcd_fb == MAP_FAILED) 
	{
		perror("申请显存失败");
		if (g_fd_lcd > 0)
		{
			close(g_fd_lcd);
			g_fd_lcd = 0;
		}
		return false;
	} 

	LCD_CLEAN(g_lcd_fb);
	if(ioctl(g_fd_lcd, FBIOGET_VSCREENINFO, &g_vinfo) != 0)/* 获取可变属性 */
	{
			perror("获取LCD设备可变属性信息失败");
			munmap(g_lcd_fb, LCD_BUFF_SIZE*LCD_BLOCK);
			if (g_fd_lcd > 0)
			{
				close(g_fd_lcd);
				g_fd_lcd = 0;
			}
			return false;
	}
	
	return true;
}

/**
 * @brief 关闭lcd释放资源
 * 
 * @return true 
 * @return false 
 */
bool lcd_close()
{
	munmap(g_lcd_fb, LCD_BUFF_SIZE*LCD_BLOCK);
  close(g_fd_lcd);
	return true;
}

/**
 * @brief 将给定的一个缓存区的的内容显示出来
 * 
 * @param fb_base 屏幕文件显存映射的基地址
 * @param x 			屏幕的起始 x 坐标
 * 			@arg	0 ~ LCD_WIDE 
 * @param y 			屏幕的起始 y 坐标
 * 			@arg  0 ~ LCD_HEIGHT
 * @param wide 		希望写入的显存宽度，为了防止超出显存范围其取值范围根据输入的起
 * 		始 x 坐标而变化。
 * 			@arg  0 ~ (LCD_WIDE - x)
 * @param height  希望写入的显存高度，为了防止超出显存范围其取值范围根据输入的起
 * 		始 y 坐标而变化。
 * 			@arg  0 ~ (LCD_HEIGHT - y)
 * @param buf 		要显示的缓存区
 * @param buf_width	缓存区中多大的宽度要写入到显存中
 * @param buf_off_l	当只要显示缓存区的部分图像时，这个变量可以设置行起点
 * @param buf_off_r	当只要显示缓存区的部分图像时，这个变量可以设置列起点
 * 
 * @note  当 buf_width > 0 时 buf_off_l 和 buf_off_r 才有效
 */
void lcd_show_buf(int32_t *fb_base, int32_t x, int32_t y, 
									int32_t wide ,int32_t height, int32_t *buf, 
									int32_t buf_width, int32_t buf_off_l, int32_t buf_off_r)
{
	/* 使起始坐标、宽度和高度都在合理范围内 */
	rational_rectangle(&x, &y, &wide, &height);

	/* 将显存映射的指针偏移到设定的起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);

	int32_t buf_width_tmp = wide;
	/* 将缓存区的指针偏移到设定的起始坐标 */
	if (buf_width > 0)
	{
		buf = buf + (buf_off_l*buf_width);
		buf_width_tmp = buf_width;
	}

  for(int32_t j = 0; j < height; j++)
  {
    for(int32_t i = 0; i < wide; i++)
    {
      *(fb_base + (j*LCD_WIDE) + i) = *(buf + (j*buf_width_tmp + buf_off_r + i));
    }
  }
}


/**
 * @brief 绘制一个实心矩形
 * 
 * @param fb_base 屏幕文件显存映射的基地址
 * @param x 			矩形的起始 x 坐标
 * 			@arg	0 ~ LCD_WIDE
 * @param y 			矩形的起始 y 坐标
 * 			@arg  0 ~ LCD_HEIGHT
 * @param wide 		希望绘制的矩形宽度，为了防止超出内存范围取值范围根据输入的起
 * 		始 x 坐标而变化。
 * 			@arg  0 ~ (LCD_WIDE - x)
 * @param height  希望绘制的矩形高度，为了防止超出内存范围取值范围根据输入的起
 * 		始 y 坐标而变化。
 * 			@arg  0 ~ (LCD_HEIGHT - y)
 * @param color 	矩形颜色,颜色在头文件 flags.h 中有定义好了常用颜色可以选择，当然
 * 		用户也可以根据自己的喜好设置颜色。每个像素的颜色由四个字节数据组成，每个字节分
 * 		别代表：透明度、红、绿、蓝。
 * 			@ref Common_colors	(in flag.h)	
 */
void lcd_rectangle(int32_t *fb_base, int32_t x, int32_t y, 
										int32_t wide ,int32_t height, int32_t color)
{
	/* 使起始坐标、宽度和高度都在合理范围内 */
	rational_rectangle(&x, &y, &wide, &height);

	/* 开始绘制 */
  for(int32_t h = y; h < ( y+height ); h++)	/* 行 */
	{
		for(int32_t w = x; w < ( x+wide ); w++)			/* 列 */
		{
			*( fb_base + ( h*LCD_WIDE+w ) ) = color;
		}
	}

	return;
}


/**
 * @brief 绘制实心圆
 * 
 * @param fb_base 	屏幕文件显存映射的基地址
 * @param center_x 	圆心的 x 坐标
 * @param center_y 	圆心的 y 坐标
 * @param R 				半径大小
 * @param color			圆形颜色,颜色在头文件 flags.h 中有定义好了常用颜色可以选择，当然
 * 		用户也可以根据自己的喜好设置颜色。每个像素的颜色由四个字节数据组成，每个字节分
 * 		别代表：透明度、红、绿、蓝。
 * 			@ref Common_colors	(in flag.h)	
 */
void lcd_circular(int32_t *fb_base, int32_t center_x, int32_t center_y, 
									int32_t R, int32_t color)
{
	int32_t r = 0;									
	int32_t line_start, row_start;	/* 起始行列 */
	int32_t line_end, row_end;			/* 结束行列 */

	/* 根据圆心和半径确定一个矩形区域，在这个区域中画圆 */
	line_start = ( center_y - R > 0 ) ? ( center_y - R ) : 0;
	row_start  = ( center_x - R > 0 ) ? ( center_x - R ) : 0;

	line_end = ( center_y + R < LCD_HEIGHT ) ? ( center_y + R ) : LCD_HEIGHT;
	row_end  = ( center_x + R < LCD_WIDE )   ? ( center_x + R ) : LCD_WIDE;

	for ( int32_t h = line_start; h < line_end; h++ )
	{
		for ( int32_t w = row_start; w < row_end; w++ )
		{
			r = ((w-center_x) * (w-center_x) + (h-center_y) * (h-center_y));
			if( r <= R*R )
				*(fb_base + ROW*h + w) = color;
		}
	}

}


/**
 * @brief 绘制圆环
 * 
 * @param fb_base 	屏幕文件显存映射的基地址
 * @param center_x 	圆心的 x 坐标
 * @param center_y 	圆心的 y 坐标
 * @param R_outer 	外圆半径
 * @param R_inner 	内圆半径
 * @param color 		圆形颜色,颜色在头文件 flags.h 中有定义好了常用颜色可以选择，当然
 * 		用户也可以根据自己的喜好设置颜色。每个像素的颜色由四个字节数据组成，每个字节分
 * 		别代表：透明度、红、绿、蓝。
 */
void lcd_ring(int32_t *fb_base, int32_t center_x, int32_t center_y,
							 int32_t R_outer, int32_t R_inner, int32_t color)
{
	int32_t r = 0;									
	int32_t line_start, row_start;	/* 起始行列 */
	int32_t line_end, row_end;			/* 结束行列 */

	/* 根据圆心和半径确定一个矩形区域，在这个区域中画圆 */
	line_start = ( center_y - R_outer > 0 ) ? ( center_y - R_outer ) : 0;
	row_start  = ( center_x - R_outer > 0 ) ? ( center_x - R_outer ) : 0;

	line_end = ( center_y + R_outer < LCD_HEIGHT ) ? 
								( center_y + R_outer ) : LCD_HEIGHT;
	row_end  = ( center_x + R_outer < LCD_WIDE )   ? 
								( center_x + R_outer ) : LCD_WIDE;

	// printf("\ncenter_x:%d\tcenter_y:%d\tR:%d\n", center_x, center_y, R);
	// printf("line_start:%d\trow_start:%d\n", line_start, row_start);
	// printf("line_end:%d\trow_end:%d\n", line_end, row_end);
	for ( int32_t h = line_start; h < line_end; h++ )
	{
		for ( int32_t w = row_start; w < row_end; w++ )
		{
			r = ((w-center_x) * (w-center_x) + (h-center_y) * (h-center_y));
			if( ( r <= R_outer*R_outer ) && ( r >= R_inner*R_inner ) )
				*(fb_base + ROW*h + w) = color;
		}
	}

}




#ifdef  SUPPORT_BMP

/**
 * @brief 将一个bmp图像的内容存到一个一片内存中
 * 
 * @param des_buf   [out] 存入的目标内存
 * @param src_path  源图像的路径
 * @param buf_width 图像缓存 des_buf 的宽度
 * @param buf_height	图像缓存 des_buf 的高度
 */
bool lcd_bmp_to_Buf (int32_t *des_buf, const int8_t *src_path, 
												uint32_t buf_width, uint32_t buf_height)
{
  int32_t fd_bmp;
  uint32_t width, height;			/* 图像的宽和高 */
  bitmap_header fileHeader;		
	bitmap_info infoHeader;

  /* 打开图片文件 */
	fd_bmp = open(src_path, O_RDWR);
	if( fd_bmp == -1 )
	{
		perror("open .bmp error");
		return false;
	}

	/* 获取文件的信息头 14个字节 */
	read(fd_bmp, &fileHeader, 14);

	/* 获取位图信息头 40 个字节 */
	read(fd_bmp, &infoHeader, 40);

	if( infoHeader.width%4 != 0 )
	{
		printf("bmp 图像的宽度必须是 4 的倍数\n");
		close(fd_bmp);
		return false;
	}
	/* 得到图像的宽和高 */
	width = infoHeader.width;
	height = infoHeader.height;
	
  if ((buf_width < width) || (buf_height < height))
  {
    printf("size is too small\n");
    return false;
  }
  // uint32_t blank = size - (width*height);  /* 计算将整个图片存入目标缓存后缓存中还有多少空位 */

	/* 读取bmp图片的RGB数据 */
	int8_t bmp_buf[width*height*3];
	int32_t ret = read(fd_bmp,bmp_buf,sizeof(bmp_buf));

	/* 图片数据的转换 */
	for (int32_t j = 0; j < height; j++)
	{
		for (int32_t i = 0; i < width; i++)
		{
			int32_t n = j*width + i;
			des_buf[(buf_height-1-j)*buf_width+i] = 
							(bmp_buf[3*n+2]<<16) + (bmp_buf[3*n+1]<<8) + bmp_buf[3*n];
		}
	}

	/* 如果缓存区还有空位就用白色填充 */
	/* 没有解决填充问题 */
	
	/* 关闭bmp图片 */
	close(fd_bmp);

  return true;
}

/**
 * @brief lcd 显示bmp图像，本功能为拓展模块，用户可在<lcd.h>中修改宏定义
 * 				SUPPORT_BMP 来裁剪。
 * 				该模块能够自适应图片显示到显示屏出界问题。
 * 
 * @param fb_base 	显存映射的基地址
 * @param bmp_name 	bmp的路径名
 * @param x 				显示的起始 x 坐标
 * @param y 				显示的起始 y 坐标
 * @return true 
 * @return false 
 * @note	bmp图像规定图像宽度必须是 4 的倍数，否则显示会变形
 */

bool lcd_show_bmp(int32_t *fb_base, const int8_t *bmp_name, int32_t x, int32_t y)
{
	int32_t	fd_bmp;
	uint32_t wide, height;			/* 实际要显示的图像的宽和高 */
	uint32_t x_end, y_end;			/* 图像的终点坐标 */
	bitmap_header fileHeader;		
	bitmap_info infoHeader;

	/* 打开图片文件 */
	fd_bmp = open(bmp_name, O_RDWR);
	if( fd_bmp == -1 )
	{
		perror("open .bmp error");
		return false;
	}

	/* 获取文件的信息头 14个字节 */
	read(fd_bmp, &fileHeader, 14);

	/* 获取位图信息头 40 个字节 */
	read(fd_bmp, &infoHeader, 40);
	// printf("宽度：%d\t高度:%d\t深度:%hd\t大小:%d\n",
	// 											infoHeader.width,
	// 											infoHeader.height,
	// 											infoHeader.bit_count,
	// 											infoHeader.size_img);

	if( infoHeader.width%4 != 0 )
	{
		printf("bmp 图像的宽度必须是 4 的倍数\n");
		close(fd_bmp);
		return false;
	}
	/* 得到图像的宽和高 */
	wide = infoHeader.width;
	height = infoHeader.height;

	/* 读取bmp图片的RGB数据 */
	int8_t bmp_buf[infoHeader.width*infoHeader.height*3];
	int32_t ret = read(fd_bmp,bmp_buf,sizeof(bmp_buf));

	/* 图片数据的转换 */
	int32_t lcd_buf[infoHeader.height*infoHeader.width];
  for(int32_t n=0; n<infoHeader.height*infoHeader.width; n++)
		*(lcd_buf+n) = (bmp_buf[3*n+2]<<16) + (bmp_buf[3*n+1]<<8) + bmp_buf[3*n];
	
	/* 关闭bmp图片 */
	close(fd_bmp);

	/* 规范图像的位置信息——处理出现图像出界的问题 */
	rational_rectangle(&x, &y, &wide, &height);

	/* 将显存映射的指针偏移到设定的起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);
	// x_end = ( ( x+wide ) >= LCD_WIDE ) ? ( LCD_WIDE-1 ) : ( x+wide );
	// y_end = ( ( y+height ) >= LCD_HEIGHT ) ? ( LCD_HEIGHT-1 ) : ( x+height );
	// printf("x:%d\ty:%d\nx_end:%d\ty_end:%d\n", x, y, x_end, y_end);

	/* 将图片数据写入显存 */
	for(int32_t j=0;j<height;j++)
	{
    for(int32_t i=0;i<wide;i++)
		{
			/* 下面的式子中的 (infoHeader.height-height) 是为了应对图像出界时的情况
				由于bmp的图像数据是以行颠倒的方式存储的，假设lcd显示图像向下出界 10 行，
				那么图像数组中的前10行数据就要跳过 */
			fb_base[(height-1-j)*LCD_WIDE+i] = 
							lcd_buf[(infoHeader.height-height+j)*infoHeader.width+i];
		}
	}		
	return true;
}

/**
 * @brief lcd 显示bmp图像，本功能为拓展模块，用户可在<lcd.h>中修改宏定义
 * 				SUPPORT_BMP 来裁剪。
 * 				该模块能够自适应图片显示到显示屏出界问题。有贴纸效果
 * 
 * @param fb_base 	显存映射的基地址
 * @param bmp_name 	bmp的路径名
 * @param x 				显示的起始 x 坐标
 * @param y 				显示的起始 y 坐标
 * @return true 
 * @return false 
 * @note	bmp图像规定图像宽度必须是 4 的倍数，否则显示会变形
 */
bool lcd_show_bmp_sticker(int32_t *fb_base, const int8_t *bmp_name, int32_t x, int32_t y)
{
	int32_t	fd_bmp;
	uint32_t wide, height;			/* 实际要显示的图像的宽和高 */
	uint32_t x_end, y_end;			/* 图像的终点坐标 */
	bitmap_header fileHeader;		/*  */
	bitmap_info infoHeader;

	/* 打开图片文件 */
	fd_bmp = open(bmp_name, O_RDWR);
	if( fd_bmp == -1 )
	{
		perror("open .bmp error");
		return false;
	}

	/* 获取文件的信息头 14个字节 */
	read(fd_bmp, &fileHeader, 14);

	/* 获取位图信息头 40 个字节 */
	read(fd_bmp, &infoHeader, 40);
	// printf("宽度：%d\t高度:%d\t深度:%hd\t大小:%d\n",
	// 											infoHeader.width,
	// 											infoHeader.height,
	// 											infoHeader.bit_count,
	// 											infoHeader.size_img);

	if( infoHeader.width%4 != 0 )
	{
		printf("bmp 图像的宽度必须是 4 的倍数\n");
		close(fd_bmp);
		return false;
	}
	/* 得到图像的宽和高 */
	wide = infoHeader.width;
	height = infoHeader.height;

	/* 读取bmp图片的RGB数据 */
	int8_t bmp_buf[infoHeader.width*infoHeader.height*3];
	int32_t ret = read(fd_bmp,bmp_buf,sizeof(bmp_buf));

	/* 图片数据的转换 */
	int32_t lcd_buf[infoHeader.height*infoHeader.width];
  for(int32_t n=0; n<infoHeader.height*infoHeader.width; n++)
		*(lcd_buf+n) = (bmp_buf[3*n+2]<<16) + (bmp_buf[3*n+1]<<8) + bmp_buf[3*n];
	
	/* 关闭bmp图片 */
	close(fd_bmp);

	/* 规范图像的位置信息——处理出现图像出界的问题 */
	rational_rectangle(&x, &y, &wide, &height);

	/* 将显存映射的指针偏移到设定的起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);
	// x_end = ( ( x+wide ) >= LCD_WIDE ) ? ( LCD_WIDE-1 ) : ( x+wide );
	// y_end = ( ( y+height ) >= LCD_HEIGHT ) ? ( LCD_HEIGHT-1 ) : ( x+height );
	// printf("x:%d\ty:%d\nx_end:%d\ty_end:%d\n", x, y, x_end, y_end);

	/* 将图片数据写入显存 */
	for(int32_t j=0;j<height;j++)
	{
    for(int32_t i=0;i<wide;i++)
		{
			if(fb_base[(height-1-j)*LCD_WIDE+i] == LCD_WHITE)
			/* 下面的式子中的 (infoHeader.height-height) 是为了应对图像出界时的情况
				由于bmp的图像数据是以行颠倒的方式存储的，假设lcd显示图像向下出界 10 行，
				那么图像数组中的前10行数据就要跳过 */
			fb_base[(height-1-j)*LCD_WIDE+i] = 
							lcd_buf[(infoHeader.height-height+j)*infoHeader.width+i];
		}
	}		
	return true;
}

/**
 * @brief lcd 显示bmp图像，本功能为拓展模块，用户可在<lcd.h>中修改宏定义
 * 				SUPPORT_BMP 来裁剪。
 * 				该模块能够自适应图片显示到显示屏出界问题。能选择忽略掉其中的一种颜色
 * 
 * @param fb_base 	显存映射的基地址
 * @param bmp_name 	bmp的路径名
 * @param x 				显示的起始 x 坐标
 * @param y 				显示的起始 y 坐标
 * @param rmcolor		要去掉的颜色
 * @return true 
 * @return false 
 * @note	bmp图像规定图像宽度必须是 4 的倍数，否则显示会变形
 */
bool lcd_show_bmp_rmColor(int32_t *fb_base, const int8_t *bmp_name, 
													int32_t x, int32_t y, int32_t rmcolor)
{
	int32_t	fd_bmp;
	uint32_t wide, height;			/* 实际要显示的图像的宽和高 */
	uint32_t x_end, y_end;			/* 图像的终点坐标 */
	bitmap_header fileHeader;		
	bitmap_info infoHeader;

	/* 打开图片文件 */
	fd_bmp = open(bmp_name, O_RDWR);
	if( fd_bmp == -1 )
	{
		perror("open .bmp error");
		return false;
	}

	/* 获取文件的信息头 14个字节 */
	read(fd_bmp, &fileHeader, 14);

	/* 获取位图信息头 40 个字节 */
	read(fd_bmp, &infoHeader, 40);
	// printf("宽度：%d\t高度:%d\t深度:%hd\t大小:%d\n",
	// 											infoHeader.width,
	// 											infoHeader.height,
	// 											infoHeader.bit_count,
	// 											infoHeader.size_img);

	if( infoHeader.width%4 != 0 )
	{
		printf("bmp 图像的宽度必须是 4 的倍数\n");
		close(fd_bmp);
		return false;
	}
	/* 得到图像的宽和高 */
	wide = infoHeader.width;
	height = infoHeader.height;

	/* 读取bmp图片的RGB数据 */
	int8_t bmp_buf[infoHeader.width*infoHeader.height*3];
	int32_t ret = read(fd_bmp,bmp_buf,sizeof(bmp_buf));

	/* 图片数据的转换 */
	int32_t lcd_buf[infoHeader.height*infoHeader.width];
  for(int32_t n=0; n<infoHeader.height*infoHeader.width; n++)
		*(lcd_buf+n) = (bmp_buf[3*n+2]<<16) + (bmp_buf[3*n+1]<<8) + bmp_buf[3*n];
	
	/* 关闭bmp图片 */
	close(fd_bmp);

	/* 规范图像的位置信息——处理出现图像出界的问题 */
	rational_rectangle(&x, &y, &wide, &height);

	/* 将显存映射的指针偏移到设定的起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);
	// x_end = ( ( x+wide ) >= LCD_WIDE ) ? ( LCD_WIDE-1 ) : ( x+wide );
	// y_end = ( ( y+height ) >= LCD_HEIGHT ) ? ( LCD_HEIGHT-1 ) : ( x+height );
	// printf("x:%d\ty:%d\nx_end:%d\ty_end:%d\n", x, y, x_end, y_end);

	/* 将图片数据写入显存 */
	for(int32_t j=0;j<height;j++)
	{
    for(int32_t i=0;i<wide;i++)
		{
			if(lcd_buf[(infoHeader.height-height+j)*infoHeader.width+i] != rmcolor)
			/* 下面的式子中的 (infoHeader.height-height) 是为了应对图像出界时的情况
				由于bmp的图像数据是以行颠倒的方式存储的，假设lcd显示图像向下出界 10 行，
				那么图像数组中的前10行数据就要跳过 */
			fb_base[(height-1-j)*LCD_WIDE+i] = 
							lcd_buf[(infoHeader.height-height+j)*infoHeader.width+i];
		}
	}		
	return true;
}														

/**
 * @brief lcd 显示压缩后的bmp图像，本功能为拓展模块，用户可在<lcd.h>中修改宏定义
 * 				SUPPORT_BMP 来裁剪。
 * 				该模块能够自适应图片显示到显示屏出界问题。
 * 
 * @param fb_base 	显存映射的基地址
 * @param bmp_name 	bmp的路径名
 * @param x 				显示的起始 x 坐标
 * @param y 				显示的起始 y 坐标
 * @param	zip				压缩倍数(建议用偶数)
 * @return true 
 * @return false 
 * @note	bmp图像规定图像宽度必须是 4 的倍数，否则显示会变形
 */
#if 1
bool lcd_show_zipbmp(int32_t *fb_base, const int8_t *bmp_name, 
											int32_t x, int32_t y , uint32_t zip)
{
	int32_t	fd_bmp;
	uint32_t wide, height;					/* 实际要显示的图像的宽和高 */
	uint32_t zip_wide, zip_height;	/* 压缩后的图像的宽和高 */
	uint32_t x_end, y_end;					/* 图像的终点坐标 */
	bitmap_header fileHeader;				/* 文件信息头 */
	bitmap_info infoHeader;					/* 位图信息头 */

	/* 打开图片文件 */
	fd_bmp = open(bmp_name, O_RDWR);
	if( fd_bmp == -1 )
	{
		perror("open .bmp error");
		return false;
	}

	/* 获取文件的信息头 14个字节 */
	read(fd_bmp, &fileHeader, 14);

	/* 获取位图信息头 40 个字节 */
	read(fd_bmp, &infoHeader, 40);
	// printf("宽度：%d\t高度:%d\t深度:%hd\t大小:%d\n",
	// 											infoHeader.width,
	// 											infoHeader.height,
	// 											infoHeader.bit_count,
	// 											infoHeader.size_img);

	if( infoHeader.width%4 != 0 )
	{
		printf("bmp 图像的宽度必须是 4 的倍数\n");
		close(fd_bmp);
		return false;
	}
	/* 得到图像的宽和高 */
	wide = infoHeader.width;
	height = infoHeader.height;

	/* 读取bmp图片的RGB数据 */
	int8_t bmp_buf[infoHeader.width*infoHeader.height*3];
	int32_t ret = read(fd_bmp,bmp_buf,sizeof(bmp_buf));

	/* 图片数据的转换 */
	int32_t lcd_buf[infoHeader.height*infoHeader.width];
  for(int32_t n=0; n<infoHeader.height*infoHeader.width; n++)
		*(lcd_buf+n) = (bmp_buf[3*n+2]<<16) + (bmp_buf[3*n+1]<<8) + bmp_buf[3*n];
	
	/* 关闭bmp图片 */
	close(fd_bmp);
	zip_height = height/4;
	zip_wide	 = wide/4;
	/* 规范图像的位置信息——处理出现图像出界的问题 */
	rational_rectangle(&x, &y, &zip_wide, &zip_height);

	/* 将显存映射的指针偏移到设定的起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);
	// x_end = ( ( x+wide ) >= LCD_WIDE ) ? ( LCD_WIDE-1 ) : ( x+wide );
	// y_end = ( ( y+height ) >= LCD_HEIGHT ) ? ( LCD_HEIGHT-1 ) : ( x+height );
	// printf("x:%d\ty:%d\nx_end:%d\ty_end:%d\n", x, y, x_end, y_end);

	/* 将图片数据写入显存 */
	for(int32_t j=0;j<zip_height;j++)
	{
    for(int32_t i=0;i<zip_wide;i++)
		{
			/* 下面的式子中的 (infoHeader.height-height) 是为了应对图像出界时的情况
				由于bmp的图像数据是以行颠倒的方式存储的，假设lcd显示图像向下出界 10 行，
				那么图像数组中的前10行数据就要跳过 */
			fb_base[(zip_height-1-j)*LCD_WIDE+i] = 
							lcd_buf[(infoHeader.height-height+j*zip)*infoHeader.width+i*zip];
		}
	}		
	return true;
}
#endif

#endif /* SUPPORT_BMP */



#ifdef USE_FONTS

/**
  * @brief  设置英文字体类型
  * @param  fonts: 指定要选择的字体
	*		参数为以下值之一
  * 	@arg：Font24x32;
  * 	@arg：Font16x24;
  * 	@arg：Font8x16;
  * @retval None
  */
void LCD_SetFont(sFONT *fonts)
{
  LCD_Currentfonts = fonts;
}

/**
  * @brief  获取当前字体类型
  * @param  None.
  * @retval 返回当前字体类型
  */
sFONT *LCD_GetFont(void)
{
  return LCD_Currentfonts;
}

/**
  * @brief  设置LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor: 指定前景(字体)颜色
  * @param  BackColor: 指定背景颜色
	* @param  use	:	 是否开启背景色
  * @retval None
  */
void LCD_SetColors(uint32_t TextColor, uint32_t BackColor, bool useBack) 
{
	useBackColor = useBack;
	if( useBackColor )
	{
		CurrentBackColor = BackColor;
	}
  CurrentTextColor = TextColor; 
}

/**
  * @brief  获取LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor [output] 用来存储前景(字体)颜色的指针变量
  * @param  BackColor [output] 用来存储背景颜色的指针变量
	* @param  useBack   [output] 背景色是否开启
  * @retval None
  */
void LCD_GetColors(uint32_t *TextColor, uint32_t *BackColor, bool *useBack)
{
  *TextColor = CurrentTextColor;
  *BackColor = CurrentBackColor;
	*useBack = useBackColor;
}

/**
  * @brief  设置LCD的前景(字体)颜色,RGB565
  * @param  Color: 指定前景(字体)颜色 
  * @retval None
  */
void LCD_SetTextColor(uint32_t Color)
{
  CurrentTextColor = Color;
}

/**
  * @brief  设置LCD的背景颜色,RGB565
  * @param  Color: 指定背景颜色 
	* @param  use	:	 是否开启背景色
  * @retval None
  */
void LCD_SetBackColor(uint32_t Color, bool use)
{
	useBackColor = use;
	if( useBackColor )
	{
		CurrentBackColor = Color;
	}
}


/**
 * @brief  在显示器上显示一个英文字符
 * @param  fb_base:显存映射的基地址
 * @param  x	 ：在特定扫描方向下字符的起始X坐标
 * @param  y	 ：在特定扫描方向下该点的起始Y坐标
 * @param  cint8_t ：要显示的英文字符
 * @note 	可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * 				tip:字模的数据中 1bit 代表一个像素点，而lcd是 32bits 代表一个像素，所以在
 * 						for循环中会有相应的转换
 * @retval None
 */
void Dispint8_t_EN ( uint32_t *fb_base, u_int16_t x, u_int16_t y, const int8_t cint8_t )
{
	u_int16_t  fontLength;	
	u_int16_t ucRelativePositon;							/* 编码表偏移量 */
	uint32_t lineFeed = 0;										/* 记录每行已显示的像素数，用来判断是否该换行 */
	u_int8_t *Pfont;

	/* 坐标大小限制 */
	x = ( x > ( LCD_WIDE-LCD_Currentfonts->Width ) ) ? 
			( LCD_WIDE-LCD_Currentfonts->Width ) : x;
	y = ( y > ( LCD_HEIGHT- LCD_Currentfonts->Height ) ) ?
			( LCD_HEIGHT- LCD_Currentfonts->Height ) : y;

/* 取字模数据 --------------------------------------------------------------------*/
	/* 对ascii码表偏移（字模表不包含ASCII表的前32个非图形符号）*/
	ucRelativePositon = cint8_t - ' ';

	/* 每个字模的字节数 */
	fontLength = (LCD_Currentfonts->Height * LCD_Currentfonts->Width)/8;
	// printf("length:%d\theight:%d\twidth:%d\n", fontLength,
	// 				LCD_Currentfonts->Height, LCD_Currentfonts->Width);
	/* 字模首地址 */
	/*ascii码表偏移值乘以每个字模的字节数，求出字模的偏移位置*/
	Pfont = (u_int8_t *)&LCD_Currentfonts->table[ucRelativePositon * fontLength];


/* 显示 -----------------------------------------------------------------------*/
	/* 将显存映射的指针偏移到起始坐标 */
	fb_base = fb_base + (y*LCD_WIDE+x);

	for( uint32_t i = 0; i < fontLength; i++)
	{
		/* 字模数据中 1bit 代表一个像素点，而lcd是 32bits 代表一个像素点 */
		for( uint32_t j = 0; j < 8; j++)
		{
			if( (Pfont[i] & (0x80 >> j)) != 0 )
			{
				fb_base[lineFeed] = CurrentTextColor;
			}
			else if( useBackColor )
			{
				fb_base[lineFeed] = CurrentBackColor;
			}	
			lineFeed++;
		}

		/* 判断是否换行 */
		if( lineFeed == LCD_Currentfonts->Width )
		{
			lineFeed = 0;
			fb_base = fb_base + LCD_WIDE;		
		}	
	
	}
}

/**
 * @brief 显示字符串
 * 
 * @param fb_base:显存映射的基地址
 * @param x	 ：在特定扫描方向下字符的起始X坐标
 * @param y	 ：在特定扫描方向下该点的起始Y坐标
 * @param str：要显示的字符串 
 * @note 	可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval None
 */
void DispString_EN ( uint32_t *fb_base, u_int16_t x, u_int16_t y, const int8_t *str )
{
	for (uint32_t i = 0; str[i] != '\0'; i++)
	{	
		// printf("%c\n", str[i]);
		/* 如果显示的字符到了屏幕最右边则换行 */
		if( x + LCD_Currentfonts->Width > LCD_WIDE)	
		{
			x = 0;
			y += LCD_Currentfonts->Height;
		}
		/* 如果显示的字符行数到了屏幕最下边则回到头重新显示 */
		if( y + LCD_Currentfonts->Height > LCD_HEIGHT )
		{
			x = 0;
			y = 0;
		}
		// printf("i:%d\tx:%d\ty:%d\n", i, x, y);
		Dispint8_t_EN(fb_base, x, y, str[i]);
		x += LCD_Currentfonts->Width;
		// getint8_t();
	}
}

/**
 * @brief 按行来显示字符串。
 * 				思路是按照当前字体大小将整个lcd屏幕划分成一个个的格子，字符的位置严格显示在格子里，
 * 				就像在文稿纸上写字一样。本函数实际上就是通过输入字符串的行数和格数转换成一个起始坐
 * 				标，再将这个坐标传给函数 DispString_EN() 而已。
 * 
 * @param fb_base 显存映射的基地址
 * @param line 		在某行显示，取值范围为 1 ～ ( LCD_HEIGHT / LCD_Currentfonts->Height )
 * @param nth 		某行中的第n个格子显示，取值范围为 1 ～ ( LCD_WIDE / LCD_Currentfonts->Width )
 * @param str 		要显示的字符串
 */
void DispStringLine_EN ( uint32_t *fb_base, u_int16_t line, u_int16_t nth, const int8_t *str )
{
	/* 显示字符的坐标 */
	uint32_t x, y;

	/* 限制位置，防止超出范围 */
	nth = ( nth < 1 ) ? 1 : nth;
	nth = ( nth > (LCD_WIDE/LCD_Currentfonts->Width ) ) ? 
					(LCD_WIDE/LCD_Currentfonts->Width) : nth;
	line = ( line < 1 ) ? 1 : line;
	line = ( line > (LCD_HEIGHT/LCD_Currentfonts->Height) ) ? 
					(LCD_HEIGHT/LCD_Currentfonts->Height) : line;
	
	nth--;	line--;		/* 用户输入时想在第一行第一格显示一个字符，这里自减转换为第0行第0个，方便编写代码而已 */

	x = nth*LCD_Currentfonts->Width;
	y = line*LCD_Currentfonts->Height;

	DispString_EN(fb_base, x, y, str);
}

/**
 * @brief 清除指定行中的第 from_nth 个格到第 to_nth 个格
 * 				清除实际上就是把目标区域用背景色覆盖，如果字体的背景色被设为无色则默认用白色覆盖
 * 
 * @param fb_base 	显存映射的基地址
 * @param line 			在某行显示，取值范围为 1 ～ ( LCD_HEIGHT / LCD_Currentfonts->Height )
 * @param from_nth 	要清除的起始格子
 * @param to_nth 		要清楚的最后一个格子
 */
void LCD_ClearLineBlock(uint32_t *fb_base, u_int16_t line, 
													u_int16_t from_nth, u_int16_t to_nth)
{
	from_nth = ( from_nth < 1 ) ? 1 : from_nth;
	from_nth = ( from_nth > (LCD_WIDE/LCD_Currentfonts->Width ) ) ? 
					(LCD_WIDE/LCD_Currentfonts->Width) : from_nth;
	to_nth = ( to_nth < from_nth ) ? from_nth : to_nth;
	to_nth = ( to_nth > (LCD_WIDE/LCD_Currentfonts->Width ) ) ? 
					(LCD_WIDE/LCD_Currentfonts->Width) : to_nth;
	line = ( line < 1 ) ? 1 : line;
	line = ( line > (LCD_HEIGHT/LCD_Currentfonts->Height) ) ? 
					(LCD_HEIGHT/LCD_Currentfonts->Height) : line;

	line--; from_nth--;	to_nth--;	/* 用户输入时想在第一行第一格显示一个字符，这里自减转换为第0行第0个，方便编写代码而已 */

	uint32_t from_x = from_nth*LCD_Currentfonts->Width;
	uint32_t to_x = to_nth*LCD_Currentfonts->Width;
	uint32_t y = line*LCD_Currentfonts->Height;
	// printf("from_x:%d\tto_x:%d\ty:%d\n", from_x, to_x, y);
	if ( useBackColor )
	{
		lcd_rectangle(fb_base, from_x, y, (to_x-from_x), LCD_Currentfonts->Height, CurrentBackColor);
	}
	else
	{
		lcd_rectangle(fb_base, from_x, y, (to_x-from_x), LCD_Currentfonts->Height, LCD_WHITE);
	}
	
}

/**
 * @brief 清除一整行
 * 
 * @param fb_base 显存映射的基地址
 * @param line 		要清楚的行
 */
void LCD_ClearLine(uint32_t *fb_base, u_int16_t line)
{
	line = ( line < 1 ) ? 1 : line;
	line = ( line > (LCD_HEIGHT/LCD_Currentfonts->Height) ) ? 
					(LCD_HEIGHT/LCD_Currentfonts->Height) : line;
	uint32_t y = (--line)*LCD_Currentfonts->Height;	
	
	if ( useBackColor )
	{
		lcd_rectangle(fb_base, 0, y, LCD_WIDE, LCD_Currentfonts->Height, CurrentBackColor);
	}
	else
	{
		lcd_rectangle(fb_base, 0, y, LCD_WIDE, LCD_Currentfonts->Height, LCD_WHITE);
	}
}													

#endif	/* USE_FONTS */

/* ----------------------- 静态函数定义的区域 ----------------------- */

/**
 * @brief 将矩形参数合理化，如果起始坐标出界，或者宽度和高度出界
 * 				就会将矩形出界的部位截取掉。
 * 
 * @param x 起始x坐标
 * @param y 起始y坐标
 * @param wide 矩形宽度
 * @param height 矩形高度
 */
static void rational_rectangle(int32_t *x, int32_t *y, 
															 int32_t *wide, int32_t *height)
{
	if (*x < 0)
	{
		*wide += *x;
		*x = 0;
	}
	else if (*x >= LCD_WIDE)
	{
		*x = LCD_WIDE - 1;
	}

	if (*y < 0)
	{
		*height += *y;
		*y = 0;
	}
	else if (*y >= LCD_HEIGHT)
	{
		*y = LCD_HEIGHT - 1;
	}
	
	*wide = ( *x+*wide > LCD_WIDE ) ? ( LCD_WHITE - *x ) : ( *wide ); 
	*height = ( *y+*height > LCD_HEIGHT ) ? ( LCD_HEIGHT - *y ) : ( *height );
}

 /*------------------------------ end of file --------------------------------*/
